package com.run.runbbs.util;


import org.apache.commons.lang3.CharUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.security.auth.callback.CallbackHandler;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

@Component
public class SensitiveFilter {

    private static final Logger logger= LoggerFactory.getLogger(SensitiveFilter.class);

    //替换符
    private static final String REPLACEMENT="***";

    //根节点
    private TrieNode rootNode=new TrieNode();

    //容器初始化此类时会执行此方法（PostConstruct）
    @PostConstruct
    public void init(){
        try(
                InputStream is=this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
                BufferedReader reader=new BufferedReader(new InputStreamReader(is));)
        {
            String keyword;
            while ((keyword=reader.readLine())!=null){
                //添加敏感词到前缀树
                this.addKeywordToTrieNode(keyword);
            }
        }catch (IOException e){
            logger.error("加载敏感词文件失败："+e.getMessage());
        }

    }

    //将敏感词添加到前缀树中
    private void addKeywordToTrieNode(String keyword){
        TrieNode tempNode=rootNode;
        for (int i = 0; i < keyword.length(); i++) {
            char c=keyword.charAt(i);
            TrieNode subNode=tempNode.getSubNode(c);
            if(subNode==null){
                //初始化子节点
                subNode=new TrieNode();
                tempNode.addSubNode(c,subNode);
            }
            //将临时节点指向子节点，进入下一轮循环
            tempNode=subNode;
            //设置结束标识
            if(i==keyword.length()-1){
                tempNode.setKeywordEnd(true);
            }
        }
    }


    //过滤敏感词
    public String filter(String text){
        if(StringUtils.isEmpty(text)){
            return null;
        }
        TrieNode tempNode=rootNode;

        int begin=0;
        int position=0;
        StringBuilder stringBuilder=new StringBuilder();

        while (position<text.length()){
            char c=text.charAt(position);

            //跳过符号  例子：￥赌￥博￥
            if(isSymbol(c)){
                //如果开头为符号,begin要往后走。
                if(tempNode==rootNode){//如果tempNode指向的是根节点，则表明是刚开始比较，begin和position都有往后走。
                    stringBuilder.append(c);//加入结果。
                    begin++;
                }
                //不管符号在开头还是中间，position都要往后走（看 ￥赌￥博￥  例子，当 begin指向赌字，position指向符号，则要++);
                position++;
                continue;
            }
            //检查前缀树的下级节点
            tempNode=tempNode.getSubNode(c);
            if(tempNode==null){ //没找到对应的下级节点
                stringBuilder.append(text.charAt(begin));//加入结果。
                position=++begin;////begin移到下一个字符，然后position移到begin一样的位置
                tempNode=rootNode;//tempNode重新指向的是根节点
            }else if(tempNode.isKeywordEnd()){//遇到结束标志位
                stringBuilder.append(REPLACEMENT);//替换为****
                begin=++position;//position移到下一个字符，然后begin移到position一样的位置
                tempNode=rootNode;//tempNode重新指向的是根节点，继续找敏感词。
            }else{//有对应对应的下级节点，position往后移。
                position++;
            }

        }
        //假如嫖娼是敏感词，若文本最后以“嫖啦啦”结尾，begin指向嫖字，上面循环结束后并没有把“嫖啦啦”放入结果。
        stringBuilder.append(text.substring(begin));
        return stringBuilder.toString();

    }

    //判断是否为符号
    private boolean isSymbol(Character c){
        //c<0X2E80||c>0x9FFF 东亚文字范围
        return !CharUtils.isAsciiAlphanumeric(c)&&(c<0X2E80||c>0x9FFF);
    }

    //定义前缀树结构
    private class TrieNode{
        //关键词结束标志
        private boolean isKeywordEnd =false;
        //子节点
        private Map<Character,TrieNode> subNodes=new HashMap<>();

        public boolean isKeywordEnd(){
            return isKeywordEnd;
        }
        public void setKeywordEnd(boolean isKeywordEnd){
            this.isKeywordEnd=isKeywordEnd;
        }
        //添加子节点
        public void addSubNode(Character character,TrieNode node){
            subNodes.put(character,node);
        }
        //获取子节点
        public TrieNode getSubNode(Character character){
            return subNodes.get(character);
        }

    }
}
